﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CSAngband {
	/* Base menu type */
	class Menu_Type {

		/* Colors for interactive menus */
		public enum CURS
		{
			UNKNOWN = 0,		/* Use gray / dark blue for cursor */
			KNOWN = 1			/* Use white / light blue for cursor */
		};

		/* Cursor colours */
		public static ConsoleColor[][] curs_attrs = new ConsoleColor[2][]
		{
			new ConsoleColor[]{ ConsoleColor.Gray, ConsoleColor.Blue },      /* Greyed row */
			new ConsoleColor[]{ ConsoleColor.White, ConsoleColor.Cyan }     /* Valid row */
		};

		/*** Predefined menu "skins" ***/

		/**
		 * Identifiers for the kind of layout to use
		 */
		public enum skin_id
		{
			SCROLL = 1,   /**< Ordinary scrollable single-column list */
			COLUMNS = 2   /**< Multicolumn view */
		}

		/**
		 * Types of predefined skins available.
		 */
		public enum menu_iter_id
		{
			/*
			 * A simple list of actions with an associated name and id.
			 * Private data: an array of menu_action
			 */
			ACTIONS = 1,

			/*
			 * A list of strings to be selected from - no associated actions.
			 * Private data: an array of const char *
			 */
			STRINGS = 2
		}

		/**
		 * Flags for menu appearance & behaviour
		 */
		public enum menu_type_flags
		{
			/* Tags are associated with the view, not the element */
			MN_REL_TAGS = 0x01,

			/* No tags -- movement key and mouse browsing only */
			MN_NO_TAGS = 0x02,

			/* Tags to be generated by the display function */
			MN_PVT_TAGS = 0x04,

			/* Tag selections can be made regardless of the case of the key pressed. 
			 * i.e. 'a' activates the line tagged 'A'. */
			MN_CASELESS_TAGS = 0x08,

			/* double tap (or keypress) for selection; single tap is cursor movement */
			MN_DBL_TAP = 0x10,

			/* no select events to be triggered */
			MN_NO_ACTION = 0x20
		}

		/**
		 * Underlying function set for displaying lists in a certain kind of way.
		 */
		public class menu_iter
		{
			public menu_iter(get_tag_func tf, valid_row_func rf, display_row_func drf, row_handler_func hf, resize_func zf) {
				get_tag = tf;
				valid_row = rf;
				display_row = drf;
				row_handler = hf;
				resize = zf;
			}
			/* Returns menu item tag (optional) */
			public delegate char get_tag_func(Menu_Type m, int oid);
			public get_tag_func get_tag;

			/*
			 * Validity checker (optional--all rows are assumed valid if not present)
 			 * Return values will be interpreted as: 0 = no, 1 = yes, 2 = hide.
 			 */
			public delegate int valid_row_func(Menu_Type menu, int oid);
			public valid_row_func valid_row;

			/* Displays a menu row */
			public delegate void display_row_func(Menu_Type menu, int oid, bool cursor, int row, int col, int width);
			public display_row_func display_row;

			/* Handle 'positive' events (selections or cmd_keys) */
			/* XXX split out into a select handler and a cmd_key handler */
			public delegate bool row_handler_func(Menu_Type menu, ui_event Event, int oid);
			public row_handler_func row_handler;

			/* Called when the screen resizes */
			public delegate void resize_func(Menu_Type m);
			public resize_func resize;
		}

		/* Class functions for menu layout */
		class menu_skin
		{
			public menu_skin(get_cursor_func gcf, display_list_func dlf, get_tag_func gtf, process_dir_func pdf) {
				get_cursor = gcf;
				display_list = dlf;
				get_tag = gtf;
				process_dir = pdf;
			}

			/* Determines the cursor index given a (mouse) location */
			public delegate int get_cursor_func(int row, int col, int n, int top, Region loc);
			public get_cursor_func get_cursor;

			/* Displays the current list of visible menu items */
			//top and region were both pointers
			public delegate void display_list_func(Menu_Type menu, int cursor, ref int top, Region r);
			public display_list_func display_list;

			/* Specifies the relative menu item given the state of the menu */
			public delegate char get_tag_func(Menu_Type menu, int pos);
			public get_tag_func get_tag;

			/* Process a direction */
			public delegate ui_event process_dir_func(Menu_Type menu, int dir);
			public process_dir_func process_dir;
		}

		public Menu_Type(skin_id skin_id, menu_iter iter)
		{
			Init(skin_id, iter);
		}

		/** Public variables **/
		string header;
		public string title;
		string prompt;

		/* Keyboard shortcuts for menu selection-- shouldn't overlap cmd_keys */
		public string selections; 

		/* String of characters that when pressed, menu handler should be called */
		/* Mustn't overlap with 'selections' or some items may be unselectable */
		public string cmd_keys;

  		/* auxiliary browser help function */
		//db was void*
		public delegate void browse_hook_func(int oid, object db, Region loc);
		public browse_hook_func browse_hook;

		/* Flags specifying the behavior of this menu (from menu_type_flags) */
		public int flags;


		/** Private variables **/

		/* Stored boundary, set by menu_layout().  This is used to calculate
		 * where the menu should be displayed on display & resize */
		public Region boundary;

		int filter_count;        /* number of rows in current view */
		int[] filter_list;  /* optional filter (view) of menu objects */

		public int count;               /* number of rows in underlying data set */
		//menu_data was void*
		public object menu_data;         /* the data used to access rows. */

		public void priv(int c, object m) {
			menu_data = m;
			count = c;
			ensure_cursor_valid();
		}

		menu_skin skin;      /* menu display style functions */
		menu_iter row_funcs; /* menu skin functions */

		/* State variables */
		public int cursor;             /* Currently selected row */
		int top;                /* Position in list for partial display */
		Region active;          /* Subregion actually active for selection */


		/**
		 * Initialise a menu, using the skin and iter functions specified.
		 */
		public void Init(skin_id skin_id, menu_iter iter)
		{
			menu_skin skin = menu_find_skin(skin_id);
			Misc.assert(skin != null, "menu skin not found!");
			Misc.assert(iter != null, "menu iter not found!");

			/* Wipe the struct */
			//memset(menu, 0, sizeof *menu); //meh

			/* Menu-specific initialisation */
			row_funcs = iter;
			this.skin = skin;
			cursor = 0;
		}

		/* Virtual function table for multi-column menu skin */
		static menu_skin menu_skin_column = new menu_skin(
			columns_get_cursor,
			display_columns,
			column_get_tag,
			column_process_direction
		);

		/* Virtual function table for scrollable menu skin */
		static menu_skin menu_skin_scroll = new menu_skin(
			scrolling_get_cursor,
			display_scrolling,
			scroll_get_tag,
			scroll_process_direction
		);
		/*
		 * Return the skin behaviour struct for a given skin ID.
		 */
		static menu_skin menu_find_skin(skin_id id)
		{
			switch (id)
			{
				case skin_id.SCROLL:
					return menu_skin_scroll;

				case skin_id.COLUMNS:
					return menu_skin_column;
			}

			return null;
		}

		/* ================== SKINS ============== */
		/* Scrolling menu */
		/* Find the position of a cursor given a screen address */
		static int scrolling_get_cursor(int row, int col, int n, int top, Region loc)
		{
			throw new NotImplementedException();
			//int cursor = row - loc.row + top;
			//if (cursor >= n) cursor = n - 1;

			//return cursor;
		}

		/* Modal display of menu */
		void display_menu_row(int pos, int top, bool cursor, int row, int col, int width)
		{
			char sel = '\0';
			int oid = pos;

			if (filter_list != null)
				oid = filter_list[oid];

			if (row_funcs.valid_row != null && row_funcs.valid_row(this, oid) == 2)
				return;

			if ((flags & (int)menu_type_flags.MN_NO_TAGS) == 0)
			{
				if ((flags & (int)menu_type_flags.MN_REL_TAGS) != 0)
					sel = skin.get_tag(this, pos);
				else if ((selections != null && selections.Length  > 0) && ((flags & (int)menu_type_flags.MN_PVT_TAGS) == 0))
					sel = selections[pos];
				else if (row_funcs.get_tag != null)
					sel = row_funcs.get_tag(this, oid);
			}

			if (sel != '\0')
			{
				/* TODO: CHECK FOR VALID */
				ConsoleColor color = curs_attrs[(int)CURS.KNOWN][cursor?1:0];
				Term.putstr(col, row, 3, color, "(" + sel +") ");
				col += 3;
				width -= 3;
			}

			row_funcs.display_row(this, oid, cursor, row, col, width);
		}


		/* Display current view of a skin */
		static void display_scrolling(Menu_Type menu, int cursor, ref int top, Region loc)
		{
			int col = loc.col;
			int row = loc.row;
			int rows_per_page = loc.page_rows;
			int n = (menu.filter_list != null) ? menu.filter_count : menu.count;
			int i;

			/* Keep a certain distance from the top when possible */
			if ((cursor <= top) && (top > 0))
			    top = cursor - 1;

			/* Keep a certain distance from the bottom when possible */
			if (cursor >= top + (rows_per_page - 1))
			    top = cursor - (rows_per_page - 1) + 1;

			/* Limit the top to legal places */
			top = Math.Min(top, n - rows_per_page);
			top = Math.Max(top, 0);

			for (i = 0; i < rows_per_page; i++)
			{
			    /* Blank all lines */
			    Term.erase(col, row + i, loc.width);
			    if (i < n)
			    {
			        /* Redraw the line if it's within the number of menu items */
			        bool is_curs = (i == cursor - top);
			        menu.display_menu_row(i + top, top, is_curs, row + i, col, loc.width);
			    }
			}

			if (cursor >= 0)
			    Term.gotoxy(col, row + cursor - top);
		}

		static char scroll_get_tag(Menu_Type menu, int pos)
		{
			throw new NotImplementedException();
			//if (menu.selections)
			//    return menu.selections[pos - menu.top];

			//return 0;
		}

		static ui_event scroll_process_direction(Menu_Type m, int dir)
		{
			ui_event eout = new ui_event();

			/* Reject diagonals */
			if(Misc.ddx[dir] != 0 && Misc.ddy[dir] != 0) {
				//this was empty. TODO: Maybe return null?
			}

			/* Forward/back */
			else if(Misc.ddx[dir] != 0)
				eout.type = Misc.ddx[dir] < 0 ? ui_event_type.EVT_ESCAPE : ui_event_type.EVT_SELECT;

			/* Move up or down to the next valid & visible row */
			else if(Misc.ddy[dir] != 0) {
				m.cursor += Misc.ddy[dir];
				eout.type = ui_event_type.EVT_MOVE;
			}

			return eout;
		}


		/*** Multi-column menus ***/

		/* Find the position of a cursor given a screen address */
		static int columns_get_cursor(int row, int col, int n, int top, Region loc)
		{
			throw new NotImplementedException();
			//int rows_per_page = loc.page_rows;
			//int colw = loc.width / (n + rows_per_page - 1) / rows_per_page;
			//int cursor = row + rows_per_page * (col - loc.col) / colw;

			//if (cursor < 0) cursor = 0;	/* assert: This should never happen */
			//if (cursor >= n) cursor = n - 1;

			//return cursor;
		}

		static void display_columns(Menu_Type menu, int cursor, ref int top, Region loc)
		{
			throw new NotImplementedException();
			//int c, r;
			//int w, h;
			//int n = menu.filter_list ? menu.filter_count : menu.count;
			//int col = loc.col;
			//int row = loc.row;
			//int rows_per_page = loc.page_rows;
			//int cols = (n + rows_per_page - 1) / rows_per_page;
			//int colw = 23;

			//Term_get_size(&w, &h);

			//if ((colw * cols) > (w - col))
			//    colw = (w - col) / cols;

			//for (c = 0; c < cols; c++)
			//{
			//    for (r = 0; r < rows_per_page; r++)
			//    {
			//        int pos = c * rows_per_page + r;
			//        bool is_cursor = (pos == cursor);

			//        if (pos < n)
			//            display_menu_row(menu, pos, 0, is_cursor,
			//                    row + r, col + c * colw, colw);
			//    }
			//}

			//if (menu.cursor >= 0)
			//    Term_gotoxy(col + (cursor / rows_per_page) * colw,
			//            row + (cursor % rows_per_page) - *top);
		}

		static char column_get_tag(Menu_Type menu, int pos)
		{
			throw new NotImplementedException();
			//if (menu.selections)
			//    return menu.selections[pos];

			//return 0;
		}

		static ui_event column_process_direction(Menu_Type m, int dir)
		{
			throw new NotImplementedException();
			//ui_event out = EVENT_EMPTY;

			//int n = m.filter_list ? m.filter_count : m.count;

			//region *loc = &m.active;
			//int rows_per_page = loc.page_rows;
			//int cols = (n + rows_per_page - 1) / rows_per_page;

			//if (ddx[dir])
			//    m.cursor += ddx[dir] * rows_per_page;
			//if (ddy[dir])
			//    m.cursor += ddy[dir];

			///* Adjust to the correct locations (roughly) */
			//if (m.cursor > n)
			//    m.cursor = m.cursor % rows_per_page;
			//else if (m.cursor < 0)
			//    m.cursor = (rows_per_page * cols) + m.cursor;

			//out.type = EVT_MOVE;
			//return out;
		}

		/* ------------------------------------------------------------------------
		 * MN_ACTIONS HELPER FUNCTIONS
		 *
		 * MN_ACTIONS is the type of menu iterator that displays a simple list of
		 * menu_actions.
		 * ------------------------------------------------------------------------ */

		static char menu_action_tag(Menu_Type m, int oid)
		{
			throw new NotImplementedException();
			//menu_action *acts = menu_priv(m);
			//return acts[oid].tag;
		}

		static int menu_action_valid(Menu_Type m, int oid)
		{
			Menu_Action[] acts = m.menu_data as Menu_Action[];

			if ((acts[oid].flags & Menu_Action.MN_ACT_HIDDEN) != 0)
			    return 2;

			return (acts[oid].name != null) ? 1 : 0; //I may have swapped these two when I converted to bool...
		}

		static void menu_action_display(Menu_Type m, int oid, bool cursor, int row, int col, int width)
		{
			throw new NotImplementedException();
			//menu_action *acts = menu_priv(m);
			//byte color = curs_attrs[!(acts[oid].flags & (MN_ACT_GRAYED))][0 != cursor];

			//display_action_aux(&acts[oid], color, row, col, width);
		}

		static bool menu_action_handle(Menu_Type m, ui_event Event, int oid)
		{
			throw new NotImplementedException();
			//menu_action *acts = menu_priv(m);

			//if (event.type == EVT_SELECT)
			//{
			//    if (!(acts.flags & MN_ACT_GRAYED) && acts[oid].action)
			//    {
			//        acts[oid].action(acts[oid].name, m.cursor);
			//        return true;
			//    }
			//}

			//return false;
		}


		/* Virtual function table for action_events */
		static menu_iter menu_iter_actions = new menu_iter(
			menu_action_tag,
			menu_action_valid,
			menu_action_display,
			menu_action_handle,
			null
		);


		/* ------------------------------------------------------------------------
		 * MN_STRINGS HELPER FUNCTIONS
		 *
		 * MN_STRINGS is the type of menu iterator that displays a simple list of 
		 * strings - no action is associated, as selection will just return the index.
		 * ------------------------------------------------------------------------ */
		static void display_string(Menu_Type m, int oid, bool cursor, int row, int col, int width)
		{
			throw new NotImplementedException();
			//const char **items = menu_priv(m);
			//byte color = curs_attrs[CURS_KNOWN][0 != cursor];
			//Term_putstr(col, row, width, color, items[oid]);
		}

		/* Virtual function table for displaying arrays of strings */
		static menu_iter menu_iter_strings = new menu_iter(
			null,              /* get_tag() */
			null,              /* valid_row() */
			display_string,    /* display_row() */
			null, 	           /* row_handler() */
			null
		);

		/**
		 * Return the menu iter struct for a given iter ID.
		 */
		public static menu_iter menu_find_iter(menu_iter_id id)
		{
			switch (id)
			{
			    case menu_iter_id.ACTIONS:
			        return menu_iter_actions;

			    case menu_iter_id.STRINGS:
			        return menu_iter_strings;
			}

			return null;
		}

		/**
		 * Set the menu cursor to the next valid row.
		 */
		public void ensure_cursor_valid() {
			int count = filter_list != null ? filter_count : this.count;

			for (int row = cursor; row < count; row++)
			{
			    if (is_valid_row(row))
			    {
			        cursor = row;
			        return;
			    }
			}

			/* If we've run off the end, without finding a valid row, put cursor
			 * on the last row */
			cursor = count - 1;
		}

		bool is_valid_row(int cursor)
		{
			int oid;
			int count = filter_list != null ? filter_count : this.count;

			if (cursor < 0 || cursor >= count)
				return false;

			oid = filter_list != null ? filter_list[cursor] : cursor;

			if (row_funcs.valid_row != null)
				return row_funcs.valid_row(this, oid) != 0;

			return true;
		}

		bool menu_calc_size()
		{
			/* Calculate term-relative positions */
			active = boundary.calculate();

			if (title != null)
			{
				active.row += 2;
				active.page_rows -= 2;
				active.col += 4;
			}

			if (header != null)
			{
				active.row++;
				active.page_rows--;
			}

			if (prompt != null)
			{
				if (active.page_rows > 1) {
					active.page_rows--;
				} else {
					int offset = prompt.Length + 2;
					active.col += offset;
					active.width -= offset;
				}
			}

			return (active.width > 0 && active.page_rows > 0);
		}

		public bool layout(Region loc)
		{
			boundary = loc;
			return menu_calc_size();
		}

		public void refresh(bool reset_screen)
		{
			int oid = cursor;
			Region loc = active;

			if (reset_screen) {;
				Utilities.screen_load();
				Utilities.screen_save();
			}

			if (filter_list != null && cursor >= 0)
				oid = filter_list[oid];

			if (title != null)
				Term.putstr(boundary.col, boundary.row, loc.width, ConsoleColor.White, title);

			if (header != null)
				Term.putstr(loc.col, loc.row - 1, loc.width, ConsoleColor.White, header);

			if (prompt != null)
				Term.putstr(boundary.col, loc.row + loc.page_rows, loc.width, ConsoleColor.White, prompt);

			if (browse_hook != null && oid >= 0)
				browse_hook(oid, menu_data, loc);

			skin.display_list(this, cursor, ref this.top, loc);
		}

		/* 
		 * Return a new position in the menu based on the key
		 * pressed and the flags and various handler functions.
		 */
		int get_cursor_key(int top, keypress key)
		{
			int i;
			int n = (this.filter_list != null) ? this.filter_count : this.count;

			if ((this.flags & (int)menu_type_flags.MN_CASELESS_TAGS) != 0)
			    key.code = (keycode_t)Char.ToUpper((char)key.code); //man that is a lot of work in C#...

			if ((this.flags & (int)menu_type_flags.MN_NO_TAGS) != 0)
			{
			    return -1;
			}
			else if ((this.flags & (int)menu_type_flags.MN_REL_TAGS) != 0)
			{
			    for (i = 0; i < n; i++)
			    {
			        char c = this.skin.get_tag(this, i);

			        if ((this.flags & (int)menu_type_flags.MN_CASELESS_TAGS) != 0 && c != '\0')
			            c = Char.ToUpper(c);

			        if (c != '\0' && c == (char)key.code)
			            return i + this.top;
			    }
			}
			else if ((this.flags & (int)menu_type_flags.MN_PVT_TAGS) == 0 && (this.selections != null && this.selections.Length > 0))
			{
			    for (i = 0; i < selections.Length; i++)
			    {
			        char c = this.selections[i];

			        if ((this.flags & (int)menu_type_flags.MN_CASELESS_TAGS) != 0)
			            c = Char.ToUpper(c);

			        if (c == (char)key.code)
			            return i;
			    }
			}
			else if (this.row_funcs.get_tag != null)
			{
			    for (i = 0; i < n; i++)
			    {
			        int oid = (this.filter_list != null) ? this.filter_list[i] : i;
			        char c = this.row_funcs.get_tag(this, oid);

			        if ((this.flags & (int)menu_type_flags.MN_CASELESS_TAGS) != 0 && c != '\0')
			            c = Char.ToUpper(c);

			        if (c != '\0' && c == (char)key.code)
			            return i;
			    }
			}

			return -1;
		}

		/**
		 * Handle navigation keypresses.
		 *
		 * Returns true if they key was intelligible as navigation, regardless of
		 * whether any action was taken.
		 */
		bool handle_keypress(ui_event ein, ref ui_event eout)
		{
			bool eat = false;
			int count = (this.filter_list != null) ? this.filter_count : this.count;

			if(eout == null)
				eout = new ui_event();

			/* Get the new cursor position from the menu item tags */
			int new_cursor = get_cursor_key(this.top, ein.key);
			if (new_cursor >= 0 && is_valid_row(new_cursor))
			{
			    if ((this.flags & (int)menu_type_flags.MN_DBL_TAP) == 0 || new_cursor == this.cursor)
			        eout.type = ui_event_type.EVT_SELECT;
			    else
			        eout.type = ui_event_type.EVT_MOVE;

			    this.cursor = new_cursor;
			}

			/* Escape stops us here */
			else if (ein.key.code == keycode_t.ESCAPE)
			    eout.type = ui_event_type.EVT_ESCAPE;

			/* Menus with no rows can't be navigated or used, so eat all keypresses */
			else if (count <= 0)
			    eat = true;

			/* Try existing, known keys */
			else if (ein.key.code == (keycode_t)' ')
			{
			    int rows = this.active.page_rows;
			    int total = count;

			    if (rows < total)
			    {
			        /* Go to start of next page */
			        this.cursor += this.active.page_rows;
			        if (this.cursor >= total - 1) this.cursor = 0;
			        this.top = this.cursor;
	
			        eout.type = ui_event_type.EVT_MOVE;
			    }
			    else
			    {
			        eat = true;
			    }
			}

			else if (ein.key.code == (keycode_t)'\n' || ein.key.code == (keycode_t)'\r')
			    eout.type = ui_event_type.EVT_SELECT;

			/* Try directional movement */
			else
			{
				int dir = Utilities.target_dir(ein.key);

				if (dir != 0)
				{
				    eout = this.skin.process_dir(this, dir);

				    if (eout.type == ui_event_type.EVT_MOVE)
				    {
				        while (!is_valid_row(this.cursor))
				        {
				            /* Loop around */
				            if (this.cursor > count - 1)
				                this.cursor = 0;
				            else if (this.cursor < 0)
				                this.cursor = count - 1;
				            else
				                this.cursor += Misc.ddy[dir];
				        }
			
				        Misc.assert(this.cursor >= 0);
				        Misc.assert(this.cursor < count);
				    }
				}
			}

			return eat;
		}


		/**
		 * Handle any menu command keys / SELECT events.
		 *
		 * Returns true if the key was handled at all (including if it's not handled
		 * and just ignored).
		 */
		bool handle_action(ui_event min)
		{
			if (row_funcs.row_handler != null)
			{
			    int oid = cursor;
			    if (filter_list != null)
			        oid = filter_list[cursor];

			    return row_funcs.row_handler(this, min, oid);
			}

			return false;
		}

		/*
		 * Handle mouse input in a menu.
		 * 
		 * Mouse output is either moving, selecting, escaping, or nothing.  Returns
		 * true if something changes as a result of the click.
		 */
		bool handle_mouse(ui_event ein, out ui_event eout)
		{
			throw new NotImplementedException();
			//int new_cursor;

			//if (!region_inside(&menu.active, in))
			//{
			//    /* A click to the left of the active region is 'back' */
			//    if (!region_inside(&menu.active, in) &&
			//            in.mouse.x < menu.active.col)
			//        out.type = EVT_ESCAPE;
			//}
			//else
			//{
			//    int count = menu.filter_list ? menu.filter_count : menu.count;

			//    new_cursor = menu.skin.get_cursor(in.mouse.y, in.mouse.x,
			//            count, menu.top, &menu.active);
	
			//    if (is_valid_row(menu, new_cursor))
			//    {
			//        if (new_cursor == menu.cursor || !(menu.flags & MN_DBL_TAP))
			//            out.type = EVT_SELECT;
			//        else
			//            out.type = EVT_MOVE;

			//        menu.cursor = new_cursor;
			//    }
			//}

			//return out.type != EVT_NONE;
		}

		/* 
		 * Run a menu.
		 *
		 * If popup is true, the screen is saved before the menu is drawn, and
		 * restored afterwards. Each time a popup menu is redrawn, it resets the
		 * screen before redrawing.
		 */
		public ui_event select(ui_event_type notify, bool popup)
		{
			ui_event min = new ui_event(); //EVENT_EMPTY;
			bool no_act = ((flags & (int)menu_type_flags.MN_NO_ACTION) == 1) ? true : false;

			Misc.assert(active.width != 0 && active.page_rows != 0);

			notify |= (ui_event_type.EVT_SELECT | ui_event_type.EVT_ESCAPE);
			if(popup) {
				Utilities.screen_save();
			}

			/* Stop on first unhandled event */
			while ((min.type & notify) == 0)
			{
				ui_event mout = new ui_event();

				refresh(popup);
				min = Utilities.inkey_ex();

				/* Handle mouse & keyboard commands */
				if (min.type == ui_event_type.EVT_MOUSE) {
					handle_mouse(min, out mout);
				} else if (min.type == ui_event_type.EVT_KBRD) {
					if (!no_act && (cmd_keys != null && cmd_keys.Length > 0) && cmd_keys.IndexOf((char)min.key.code) >= 0 && handle_action(min))
						continue;

					handle_keypress(min, ref mout);
				} else if (min.type == ui_event_type.EVT_RESIZE) {
					menu_calc_size();
					if (row_funcs.resize != null)
						row_funcs.resize(this);
				}

				/* XXX should redraw menu here if cursor has moved */

				/* If we've selected an item, then send that event out */
				if (mout.type == ui_event_type.EVT_SELECT && !no_act && handle_action(mout))
					continue;

				/* Notify about the outgoing type */
				if ((notify & mout.type) != (ui_event_type)0) {
					if(popup) {
						Utilities.screen_load();
					}
					return mout;
				}
			}

			if(popup) {
				Utilities.screen_load();
			}
			return min;
		}


	}
}
